package com.qianxun.framework.util;

import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;

import java.io.*;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Stream;

/**
 * 文件工具
 * @author huwei
 * @version 1.0
 */
public class FileUtil {
    /**
     * 分隔符char 表现形式
     */
    public static final char SEPARATOR_CHAR = '/' ;
    /**
     * 分隔符号
     */
    public static final String SEPARATOR = "" + SEPARATOR_CHAR;

    public static String classPath(){
       return classPath(FileUtil.class);
    }

    /**
     * 获取当前项目的calssPath路径
     * @return
     */
	public static String classPath(Class<?> cls){
        String temp = cls.getResource("").getPath();
        String suffix = SEPARATOR + FileUtil.class.getName().replace("." ,SEPARATOR).replace(FileUtil.class.getSimpleName() ,"");
        temp = temp.replace(suffix ,"");
        if(temp.endsWith("!")){
            //为jar启动时的情况
            temp = temp.replace("!" ,"").replace("file:" ,"");
        }else{
            temp += SEPARATOR;
        }
        return temp;
    }

    /**
     * 遍历获取指定文件目录下的所有文件
     * @param dir
     * @return
     */
    public static Set<String> fileListAsRecursion(String dir){
        return fileListAsRecursion(dir ,null);
    }

    /**
     * 遍历获取指定文件目录下的所有文件
     * @param dir
     * @param filter
     * @return
     */
    public static Set<String> fileListAsRecursion(String dir ,Filter filter) {
        if (dir == null || dir.trim().equals("")) {
            return new HashSet<>(0);
        }
        dir = switchSeparator(dir);

        //绝对路径
        File dirFile = new File(dir);
        if(dirFile.exists()){
            return fileListAsRecursion(dirFile ,filter);
        }
        //先对路径
        // 项目根目录
        String root = classPath();

        //jar文件启动
        if(root.endsWith(".jar")){
            String jarPath = dir.startsWith("classpath:") ? dir.replace("classpath:" ,"") : dir;
            return fileListInJarAsRecursion(root ,root + "!/" + jarPath ,filter);
        }

        // 当前项目为普通java应用程序
        // path路径中包含classpath指定，将classpath替换为/target/classes/目录
        String targetPath = dir.startsWith("classpath:")
                ? root + dir.substring(dir.indexOf(":") + 1)
                : root.replace(wrapPath("target" ,"classes"),"") + dir;
        dirFile = new File(targetPath);
        if(dirFile.exists()){
            return fileListAsRecursion(dirFile ,filter);
        }

        //尝试移除路径中的resources/路径
        dirFile = new File(targetPath.replace(wrapPath("resources"), ""));
        if(dirFile.exists()){
            return fileListAsRecursion(dirFile ,filter);
        }

        //检索是否以web 方式运行
        //不是以web 方式运行
        if(root.indexOf("WEB-INF") == -1) {
            return new HashSet<>(0);
        }

        // 当前项目以运行在web容器中
        String webRoot = root.substring(0, root.indexOf("WEB-INF"));
        // path路径中包含classpath指定，将classpath替换为/WEB-INF/classes/目录
        String webPath = dir.startsWith("classpath:") ? wrapPath("WEB-INF" ,"classes") + dir.substring(dir.indexOf(":") + 1) : dir;
        //源路径
        dirFile = new File(wrapPath(webRoot ,webPath));
        if(dirFile.exists()){
            return fileListAsRecursion(dirFile ,filter);
        }

        //尝试移除resources/路径
        String webPath2 = webPath.replace(wrapPath("resources"), "");
        dirFile = new File(wrapPath(webRoot ,webPath2));
        if(dirFile.exists()){
            return fileListAsRecursion(dirFile ,filter);
        }

        //path路径中包含src/main/webapp/指定，尝试将截取src/main/webapp/之后的路径
        String webPath3 = dir.startsWith(wrapPath("src" ,"main" ,"webapp"))
                ? detectAndGetPathName(root, wrapPath("src" ,"main" ,"webapp")) + dir.substring(dir.indexOf(wrapPath("src" ,"main" ,"webapp")) + wrapPath("src" ,"main" ,"webapp").length())
                : dir;
        dirFile = new File(wrapPath(webRoot ,webPath3));
        if(dirFile.exists()){
            return fileListAsRecursion(dirFile ,filter);
        }
        return new HashSet<>(0);
    }

    public static Set<String> fileListInJarAsRecursion(String root ,String jarDirPath ,Filter filter) {
        try {
            if (!ObjectUtil.isBlank(jarDirPath) && jarDirPath.indexOf(".jar") != -1) {
                jarDirPath = switchSeparator(jarDirPath);

                jarDirPath = jarDirPath.startsWith("jar:file:") ? jarDirPath.substring("jar:file:".length()) : jarDirPath;

                String masterJar = jarDirPath.substring(0, jarDirPath.indexOf(".jar") + ".jar".length());
                String masterDirPath = masterJar.length() == jarDirPath.length() ? "" : jarDirPath.substring(masterJar.length());
                masterDirPath = masterDirPath.startsWith("!/") ? masterDirPath.substring(2) : masterDirPath;
                if (masterDirPath.indexOf(".jar") != -1) {
                    String subJar = masterDirPath.substring(0, masterDirPath.indexOf(".jar") + ".jar".length());
                    String subDirPath = masterDirPath.substring(subJar.length());
                    subDirPath = subDirPath.startsWith("!/") ? subDirPath.substring(2) : (subDirPath.startsWith("/") ? subDirPath.substring(1) : subDirPath);
                    subJar = subJar.startsWith("!/") ? subJar.substring(2) : (subJar.startsWith("/") ? subJar.substring(1) : subJar);

                    try {
                        ZipFile zipFile = new ZipFile(new File(masterJar));
                        for (Enumeration<? extends ZipEntry> entries = zipFile.getEntries(); entries.hasMoreElements(); ) {
                            ZipEntry zipEntry = entries.nextElement();
                            String patName = zipEntry.getName();
                            patName = patName.startsWith("/") ? patName.substring(1) : patName;
                            if (subJar.equals(patName)) {
                                String subJarName = subJar.indexOf("/") != -1 ? subJar.substring(subJar.lastIndexOf("/") + 1) : subJar;
                                String tempDir = "/application/temp/fileList/" + UUID.randomUUID().toString().replace("-", "");
                                //创建临时目录
                                File dir = new File(tempDir);
                                dir.mkdirs();
                                byte[] buf = FileUtil.read(zipFile.getInputStream(zipEntry));
                                FileUtil.write(tempDir + "/" + subJarName, buf);

                                Set<String> res = fileListInJarAsRecursion(root, tempDir + "/" + subJarName + "!/" + subDirPath, filter);

                                delAllFile(new File("/application/temp/fileList/"));
                                return res;
                            }
                        }
                    } catch (Exception e) {
                    }
                } else {
                    JarFile jar = null;
                    try {
                        jar = new JarFile(new File(masterJar));
                    } catch (IOException e) {
                        return new HashSet<>(0);
                    }
                    root = root.endsWith(masterDirPath) ? root.substring(0, root.length() - masterDirPath.length()) : root;
                    root = root.startsWith("jar:file:") ? root.substring("jar:file:".length()) : root;
                    root = root.substring(0, root.lastIndexOf(".jar") + ".jar".length());
                    Set<String> res = new HashSet<>();
                    Enumeration<JarEntry> entries = jar.entries();
                    while (entries.hasMoreElements()) {
                        JarEntry entry = entries.nextElement();
                        String pathName = entry.getName();
                        if (!pathName.endsWith("/")) {
                            if (pathName.charAt(0) == SEPARATOR_CHAR) {
                                pathName = pathName.substring(1);
                            }
                            String absPath = root + "!/" + pathName;
                            String name = pathName.lastIndexOf(SEPARATOR) != -1 ? pathName.substring(pathName.lastIndexOf(SEPARATOR) + 1) : pathName;
                            if ((pathName.startsWith(masterDirPath) || masterDirPath.equals("")) && filter(filter, absPath, pathName, name)) {
                                res.add("jar:file:" + absPath);
                            }
                        }
                    }
                    try {
                        jar.close();
                    } catch (IOException e) {
                    }
                    return res;
                }

            } else {
                return fileListAsRecursion(jarDirPath, filter);
            }
            return new HashSet<>();
        }finally {
            delAllFile(new File("/application/temp/fileList/"));
        }
    }

    /**
     * 遍历获取指定文件目录下的所有文件
     * @param dirFile
     * @return
     */
    public static Set<String> fileListAsRecursion(File dirFile){
        return fileListAsRecursion(dirFile ,null);
    }

    /**
     * 遍历获取指定文件目录下到而所有文件
     * @param dirFile
     * @param filter
     * @return
     */
    public static Set<String> fileListAsRecursion(File dirFile ,Filter filter){
        if(dirFile == null || !dirFile.exists()){
            return new HashSet<>(0);
        }
        if(dirFile.isFile()){
            if(filter(filter ,dirFile.getAbsolutePath() ,dirFile.getPath() ,dirFile.getName())) {
                return Stream.of(dirFile.getAbsolutePath()).collect(HashSet::new, (res, ele) -> res.add(switchSeparator(ele)), (l, r) -> {
                });
            }
            return new HashSet<>(0);
        }
        Set<String> res = new HashSet<>();
        File[] files = dirFile.listFiles();
        for(int i = 0 ;i < files.length ;++i){
            Set<String> children = fileListAsRecursion(files[i] ,filter);
            if(children != null) {
                res.addAll(children);
            }
        }
        return res;
    }

    /**
     * 读取指定的路径文件，该方法将以：绝对路径 -> 普通java应用 -> web应用 的顺序解析文件路径，并获取文件数据，
     * 失败抛出异常；
     * 注意jarName需要与打包后的jarName一致；
     * 示例：
     * try{
     * 	byte[] res = FileUtil.read("classpath:resources/a.name" ,"jarName");
     * } catch(IOException e){
     * }
     * 示例说明：
     * classpath路径说明：
     * 1，普通java应用中：如果当前根目录中包含target/classes/，则表示项目根目录与target/classes/组合后的目录下，对应项目
     * 文件中的：src/mian/java 与 src/main/resources目录下的所有文件； 反之，如果不存在target/classes ，即手动编译
     * 的项目，则从根目录开始；
     * 2，web应用中： classpath表示src/mian/java 与 src/main/resources目录下的所有文件,而这些文件保存在root与
     * WEB-INF/classes组合后的目录下；
     * @param pathName 路径文件
     * @return
     * @throws IOException
     */
    public static byte[] read(String pathName) throws IOException {
        if (pathName == null || pathName.trim().equals("")) {
            throw new IOException("指定文件路径为空，请检测参数pathName：" + pathName);
        }
        pathName = switchSeparator(pathName);
        // 绝对路径
        try {
            return read(new File(pathName));
        } catch (IOException e) {
            // TODO: handle exception
        }

        // 项目根目录
        String root = classPath();
        //jar文件启动
        if(root.endsWith(".jar") || pathName.startsWith("jar:file:") || pathName.indexOf(".jar!/") != -1){
            String temp = pathName;
            if(!pathName.startsWith("jar:file:") && pathName.indexOf(".jar!/") == -1){
                //去掉classpath:路径
                String jarPath = temp.startsWith("classpath:") ? temp.replace("classpath:" ,"") : temp;
                temp = root + "!/" + jarPath;
            }
            return readFileInJar(temp);
        }

        // 当前项目为普通java应用程序
        // path路径中包含classpath指定，将classpath替换为/target/classes/目录
        String targetPath = pathName.startsWith("classpath:")
                ? root + pathName.substring(pathName.indexOf(":") + 1)
                : root.replace(wrapPath("target" ,"classes") ,"") + pathName;
        try {
            return read(new File(targetPath));
        } catch (IOException e) {
            // TODO: handle exception
        }

        //尝试移除路径中的resources/路径
        try {
            return read(new File(targetPath.replace(wrapPath("resources"), "")));
        } catch (IOException e) {
            // TODO: handle exception
        }

        //不是以web 方式运行
        if(root.indexOf("WEB-INF") == -1) {
            throw new IOException("未找到指定文件，请检测参数pathName:" + pathName);
        }

        // 当前项目以运行在web容器中
        String webRoot = root.substring(0, root.indexOf("WEB-INF"));
        // path路径中包含classpath指定，将classpath替换为/WEB-INF/classes/目录
        String webPath = pathName.startsWith("classpath:") ? wrapPath("WEB-INF" ,"classes") + pathName.substring(pathName.indexOf(":") + 1) : pathName;
        //源路径
        File file = new File(wrapPath(webRoot ,webPath));
        try {
            return read(file);
        } catch (IOException e) {
            // TODO: handle exception
        }
        //移除resources/路径
        String webPath2 = webPath.replace(wrapPath("resources"), "");
        file = new File(wrapPath(webRoot ,webPath2));
        try {
            return read(file);
        } catch (IOException e) {
            // TODO: handle exception
        }
        //path路径中包含src/main/webapp/指定，将截取src/main/webapp/之后的路径
        String webPath3 = pathName.startsWith(wrapPath("src" ,"main" ,"webapp"))
                ? detectAndGetPathName(root, wrapPath("src" ,"main" ,"webapp")) + pathName.substring(pathName.indexOf(wrapPath("src" ,"main" ,"webapp")) + wrapPath("src" ,"main" ,"webapp").length())
                : pathName;
        file = new File(wrapPath(webRoot ,webPath3));
        return read(file);
    }

    /**
     * 读取jar内部文件
     * @param jarPath
     * @return
     * @throws IOException
     */
    private static byte[] readFileInJar(String jarPath ) throws IOException {
        try {
            if (!ObjectUtil.isBlank(jarPath) && jarPath.indexOf(".jar") != -1) {
                jarPath = FileUtil.switchSeparator(jarPath);
                String jarAbsPath = jarPath.substring(0, jarPath.indexOf(".jar") + ".jar".length());
                jarAbsPath = jarAbsPath.startsWith("jar:file") ? jarAbsPath.substring("jar:file:".length()) : jarAbsPath;
                if (jarAbsPath.length() == jarPath.length()) {
                    return FileUtil.read(new File(jarAbsPath));
                } else {
                    String targetPath = jarPath.substring(jarPath.indexOf(".jar") + ".jar".length());
                    targetPath = targetPath.startsWith("!/") ? targetPath.substring(2) :(targetPath.startsWith("/") ?targetPath.substring(1) : targetPath);
                    if (targetPath.indexOf(".jar") != -1) {
                        String subJar = targetPath.substring(0, targetPath.indexOf(".jar") + ".jar".length());
                        String subTargetPath = targetPath.substring(subJar.length());
                        subJar = subJar.startsWith("!/") ? subJar.substring(2) : (subJar.startsWith("/") ? subJar.substring(1) : subJar);

                        byte[] res = null;
                        ZipFile zipFile = new ZipFile(new File(jarAbsPath));
                        for (Enumeration<? extends ZipEntry> entries = zipFile.getEntries(); entries.hasMoreElements(); ) {
                            ZipEntry zipEntry = entries.nextElement();
                            String patName = zipEntry.getName();
                            patName = patName.startsWith("/") ? patName.substring(1) : patName;
                            if (subJar.equals(patName)) {
                                String subJarName = subJar.indexOf("/") != -1 ? subJar.substring(subJar.lastIndexOf("/") + 1) : subJar;
                                String tempDir = "/application/temp/read/" + UUID.randomUUID().toString().replace("-", "");
                                //创建临时目录
                                File dir = new File(tempDir);
                                dir.mkdirs();
                                byte[] buf = FileUtil.read(zipFile.getInputStream(zipEntry));
                                FileUtil.write(tempDir + "/" + subJarName, buf);


                                subTargetPath = subTargetPath.startsWith("!/") ? subTargetPath.substring(2) : (subTargetPath.startsWith("/") ? subTargetPath.substring(1) : subTargetPath);
                                res = readFileInJar(tempDir + "/" + subJarName + "!/" + subTargetPath);
                                break;
                            }
                        }
                        zipFile.close();
                        ZipFile.closeQuietly(zipFile);
                        delAllFile(new File("/application/temp/read/" ));
                        if(res == null) {
                            throw new IOException("未找到指定文件，路径：" + jarPath);
                        }else{
                            return res;
                        }
                    } else {
                        byte[] res = null;
                        ZipFile zipFile = new ZipFile(new File(jarAbsPath));
                        for (Enumeration<? extends ZipEntry> entries = zipFile.getEntries(); entries.hasMoreElements(); ) {
                            ZipEntry zipEntry = entries.nextElement();
                            String patName = zipEntry.getName();
                            patName = patName.startsWith("/") ? patName.substring(1) : patName;
                            if (targetPath.equals(patName) ) {
                                res = FileUtil.read(zipFile.getInputStream(zipEntry));
                                break;
                            }
                        }
                        zipFile.close();
                        ZipFile.closeQuietly(zipFile);
                        if(res == null) {
                            throw new IOException("未找到指定文件，路径：" + jarPath);
                        }else{
                            return res;
                        }
                    }
                }
            } else {
                return read(jarPath);
            }
        }finally {
            delAllFile(new File("/application/temp/read/"));
        }
    }

    /**
     * 从给定file中读取数据，失败抛出IO异常，反之，结果非空
     * @param file 读取文件
     * @return 失败抛出IO异常，反之，结果非空
     * @throws IOException
     */
    public static byte[] read(File file) throws IOException {
        if (file == null || !file.exists() || file.isDirectory()) {
            throw new IOException("文件为空或指定文件不存在 ，请检测参数file ：" + file);
        }
        return read(new FileInputStream(file));
    }

    public static byte[] read(InputStream is) throws IOException {
        byte[] res;
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] temp = new byte[1024];
        int n = 0;
        while ((n = is.read(temp)) != -1){
            byteArrayOutputStream.write(temp ,0 ,n);
        }
        res = byteArrayOutputStream.toByteArray();
        byteArrayOutputStream.close();
        is.close();
        return res;
    }

    /**
     * 存入数据
     * @param path
     * @param buf
     */
    public static void write(String path, byte[] buf) throws IOException {
        write(path, buf ,false);
    }

    /**
     * 存入数据
     * @param path
     * @param buf
     * @param append
     * @throws IOException
     */
    public static void write(String path ,byte[] buf ,boolean append) throws IOException {
        write(new File(path) ,buf ,append);
    }

    /**
     * 存入数据
     * @param file
     * @param buf
     * @throws IOException
     */
    public static void write(File file ,byte[] buf)throws IOException{
        write(file ,buf ,false);
    }

    /**
     * 存入数据
     * @param file
     * @param buf
     * @param append
     * @throws IOException
     */
    public static void write(File file ,byte[] buf ,boolean append)throws IOException{
        write(new FileOutputStream(file ,append) ,buf);
    }

    /**
     * 存入数据
     * @param os
     * @param buf
     * @throws IOException
     */
    public static void write(OutputStream os ,byte[] buf) throws IOException {
        os.write(buf);
        os.flush();
        os.close();
    }

    /**
     * 删除文件或文件夹
     * @param directory
     */
    public static void delAllFile(File directory){
        if (!directory.isDirectory()){
            directory.delete();
        } else{
            File [] files = directory.listFiles();
            // 空文件夹
            if (files.length == 0){
                directory.delete();
                return;
            }
            // 删除子文件夹和子文件
            for (File file : files){
                if (file.isDirectory()){
                    delAllFile(file);
                } else {
                    file.delete();
                }
            }
            // 删除文件夹本身
            directory.delete();
        }
    }

    /**
     * 修改分割符号
     */
    public static String switchSeparator(String path) {
        return path.replace("\\\\", SEPARATOR).replace("\\" ,SEPARATOR);
    }

    /**
     * 封装路径
     * @param pathElements
     * @return
     */
    public static String wrapPath(String... pathElements){
        StringBuffer sb = new StringBuffer();
        if(pathElements != null && pathElements.length > 0){
            for(String ele : pathElements){
                sb.append(ele).append(SEPARATOR);
            }
        }
        return sb.toString();
    }

    /**
     * 将指定文件解压到指定目录下
     * @param pathName
     * @param targetDirPath
     * @throws IOException
     */
    public static void unZip(String pathName ,String targetDirPath) throws IOException {
        unZip(new File(pathName) ,targetDirPath);
    }

    /**
     * 将压缩文件解压到指定目录下
     * @param file
     * @param targetDirPath
     * @throws IOException
     */
    public static void unZip(File file ,String targetDirPath) throws IOException {
        ZipFile zipFile = new ZipFile(file);
        for (Enumeration<? extends ZipEntry> entries = zipFile.getEntries(); entries.hasMoreElements(); ) {
            ZipEntry zipEntry = entries.nextElement();
            InputStream is = zipFile.getInputStream(zipEntry);
            String patName = zipEntry.getName();
            if(patName.endsWith("/")){
                new File( targetDirPath+ "/" + patName).mkdirs();
            }else {
                String name = patName.indexOf("/") != -1 ? patName.substring(patName.lastIndexOf("/") + 1) : patName;
                String path = name.length() == patName.length() ? "" : patName.substring(0, patName.lastIndexOf("/"));
                new File(targetDirPath + "/" + path).mkdirs();
                FileUtil.write(targetDirPath + "/" + patName, FileUtil.read(is));
            }
        }
    }



    private static boolean filter(Filter filter,String absPath ,String relativePath ,String fileName){
        return filter == null ? true : filter.filter(absPath ,relativePath ,fileName);
    }

    /**
     * 从给定root路径下查找target文件或目录是否存储
     * @param root root路径，该路径为绝对路径
     * @param target 目标文件或目录
     * @return 存在返回target表示字符，反之，返回空串
     */
    private static String detectAndGetPathName(String root ,String target) {
        return detectAndGetPathName(root, target, "");
    }
    /**
     * 从给定root路径下查找target文件或目录是否存储
     * @param root root路径，该路径为绝对路径
     * @param target 目标文件或目录
     * @param defaultValue 自定义默认返回值
     * @return 存在返回target表示字符，反之，返回空串
     */
    private static String detectAndGetPathName(String root ,String target ,String defaultValue) {
        File file = new File(root + SEPARATOR + target);
        if(file.exists()) {
            return target;
        }
        return defaultValue;
    }

    /**
     * 文件过滤
     */
    @FunctionalInterface
    public interface Filter{
        /**
         * 文件过滤
         * @param absPath 文件绝对路径
         * @param relativePath 文件相对路径
         * @param fileName 文件名
         * @return
         */
        boolean filter(String absPath, String relativePath, String fileName);
    }
}
